package java2d;


import org.jbox2d.common.MathUtils;
import org.jbox2d.common.Vec2;

/**
 * @author liweiping
 * @date 2020/6/16 17:18
 */
public class BattleUtils {
    private static final double[] cacheSin;
    private static final double[] cacheCos;
    /**
     * float转int精确度乘法因子
     */
    public static final float VIEW_FORMATTER = 10000f;

    static {
        int degree = 3601;

        cacheSin = new double[degree];
        cacheCos = new double[degree];

        for (int i = 0; i < degree; i++) {
            cacheSin[i] = Math.sin(Math.toRadians(i * 0.1));
            cacheCos[i] = Math.cos(Math.toRadians(i * 0.1));
        }
    }

    /**
     * 长度的平方
     *
     * @param vec1
     * @param vec2
     * @return
     */
    public static float lengthSquared(Vec2 vec1, Vec2 vec2) {
        return lengthSquared(vec1.x, vec1.y, vec2.x, vec2.y);
    }

    /**
     * 向量旋转
     * 对于任意点A(x,y)，A非原点，绕原点旋转θ角后点的坐标为：(x*cosθ- y * sinθ, y*cosθ + x * sinθ)，
     * 1°为π/180弧度
     *
     * @param vec2   向量
     * @param angdeg 角度 逆时针时θ为正数， 顺时针是θ为负数
     * @return
     */
    public static Vec2 rotate(Vec2 vec2, int angdeg) {
        float radians = (float) Math.toRadians(angdeg);
        return rotate(vec2, radians);
    }

    /**
     * radians 弧度
     *
     * @param vec2
     * @param radians
     * @return
     * @desc 圆心(a, b)，已知点A(x,y)，逆时针转动弧度c（若是顺时针，则把c改成-c即可），点A变成点B(x',y')。
     * 设圆半径为r，圆心与A的连线与x轴夹角的弧度为d，则有
     * x-a=r*cosd
     * y-b=r*sind
     * 所以，
     * x'-a=r*cos(d+c)=r*cosd*cosc-r*sind*sinc=(x-a)cosc-(y-b)sinc
     * y'-b=r*sin(d+c)=r*sind*cosc+r*cosd*sinc=(y-b)cosc+(x-a)sinc
     * 所以，
     * x'=(x-a)cosc-(y-b)sinc+a
     * y'=(y-b)cosc+(x-a)sinc+b
     */
    public static Vec2 rotate(Vec2 vec2, float radians) {
        float x = vec2.x * MathUtils.cos(radians) - vec2.y * MathUtils.sin(radians);
        float y = vec2.y * MathUtils.cos(radians) + vec2.x * MathUtils.sin(radians);
        return new Vec2(x, y);
    }

    /**
     * 长度的平方
     *
     * @param x1
     * @param y1
     * @param x2
     * @param y2
     * @return
     */
    public static float lengthSquared(float x1, float y1, float x2, float y2) {
        float xx = x1 - x2;
        float yy = y1 - y2;
        return xx * xx + yy * yy;
    }

    /**
     * 长度的平方
     * @param point1
     * @param point2
     * @return
     */
    public static float lineSpace(Vec2 point1, Vec2 point2) {
        return lineSpace(point1.x, point1.y, point2.x, point2.y);
    }

    /**
     * 计算两点之间的距离
     * @param x1
     * @param y1
     * @param x2
     * @param y2
     * @return
     */
    public static float lineSpace(float x1, float y1, float x2, float y2) {
        float lineLength = 0;
        // 求平方根函数
        lineLength = MathUtils.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
        return lineLength;
    }

    // 点到线段的最短距离的判断 point0 到由两点组成的线段 point1, point2
    public static double pointToLine(Vec2 point1, Vec2 point2, Vec2 point0) {
        return pointToLine(point1.x, point1.y, point2.x, point2.y, point0.x, point0.y);
    }

    // 点到线段的最短距离的判断 点（x0,y0） 到由两点组成的线段（x1,y1） ,( x2,y2 )
    public static double pointToLine(float x1, float y1, float x2, float y2, float x0, float y0) {
        float space = 0;
        float a, b, c;
        a = lineSpace(x1, y1, x2, y2);// 线段的长度
        b = lineSpace(x1, y1, x0, y0);// (x1,y1)到点的距离
        c = lineSpace(x2, y2, x0, y0);// (x2,y2)到点的距离
        if (c <= 0.000001 || b <= 0.000001) {
            space = 0;
            return space;
        }
        if (a <= 0.000001) {
            space = b;
            return space;
        }
        if (c * c >= a * a + b * b) {
            space = b;
            return space;
        }
        if (b * b >= a * a + c * c) {
            space = c;
            return space;
        }
        float p = (a + b + c) / 2;// 半周长

        // 海伦公式求面积
        float res = p * (p - a) * (p - b) * (p - c);
        float s = res < 0 ? 0 : MathUtils.sqrt(res);

        space = 2 * s / a;// 返回点到线的距离（利用三角形面积公式求高）
        return space;
    }

    // 矢量法：点到线段的最短距离的判断 点（x0,y0） 到由两点组成的线段（x1,y1） ,( x2,y2 )
//    https://www.cnblogs.com/flyinggod/p/9359534.html
//    内积
    public static double pointToLine2(float x1, float y1, float x2, float y2, float x0, float y0) {
        float ab = lengthSquared(x1, y1, x2, y2);
        float r = ((x0 - x1) * (x2 - x1) + (y0 - y1) * (y2 - y1)) / ab;
        if (r <= 0) {
            return lineSpace(x1, y1, x0, y0);
        } else if (r >= 1) {
            return lineSpace(x2, y2, x0, y0);
        } else {
            // r  * r * ab 是 a 到锤点的长度的平方
            return Math.sqrt(lengthSquared(x1, y1, x0, y0) - r * r * ab);
        }
    }


    /**
     * 角度约束到+-360
     */
    public static float normalizeDegreeAngle(float angle) {
        if (angle >= 0 && angle < 360) {
            return angle;
        }
        angle %= 360;
        if (angle < 0) {
            angle += 360;
        }
        return angle;
    }

    /**
     * 弧度约束到0-2π
     */
    public static float normalizeRadianAngle(float radians) {
        if (radians >= 0 && radians < 2 * MathUtils.PI) {
            return radians;
        }
        radians %= 2 * MathUtils.PI;
        if (radians < 0) {
            radians += 2 * MathUtils.PI;
        }
        return radians;
    }

    /**
     * 计算两点确定向量的θ（弧度）
     */
    public static float getRadianAngle(int fromX, int fromY, int toX, int toY) {

        float angle = (float) Math.atan2(toY - fromY, toX - fromX);
        if (angle < 0) {
            angle = MathUtils.PI * 2f + angle;
        }
        return angle;
    }

    public static float getRadianAngle(float fromX, float fromY, float toX, float toY) {
        float angle = MathUtils.atan2(toY - fromY, toX - fromX);
        if (angle < 0) {
            angle = MathUtils.PI * 2f + angle;
        }
        return angle;
    }

    /**
     * 计算两点确定向量的角度
     */
    public static float getDegreeAngle(int fromX, int fromY, int toX, int toY) {
        return (float) Math.toDegrees(getRadianAngle(fromX, fromY, toX, toY));
    }

    public static int getDegreeAngleInt(int fromX, int fromY, int toX, int toY) {
        return (int) Math.toDegrees(getRadianAngle(fromX, fromY, toX, toY));
    }


    public static float getDegreeAngle(float fromX, float fromY, float toX, float toY) {
        return getDegreeAngle((int) (fromX * VIEW_FORMATTER),
                (int) (fromY * VIEW_FORMATTER), (int) (toX * VIEW_FORMATTER),
                (int) (toY * VIEW_FORMATTER));
    }

    public static int getDegreeAngleBetweenInt(float fromAngle, float toAngle) {
        return (int) getDegreeAngleBetween(fromAngle, toAngle);
    }

    /**
     * 两向量的夹角，单位角度
     */
    public static float getDegreeAngleBetween(float fromAngle, float toAngle) {
        fromAngle = normalizeDegreeAngle(fromAngle);
        toAngle = normalizeDegreeAngle(toAngle);

        if (fromAngle < toAngle) {
            return toAngle - fromAngle;
        }
        if (fromAngle == toAngle) {
            return 0;
        }
        return 360 - fromAngle + toAngle;
    }

    /**
     * 计算弧度差
     */
    public static float getRadianAngleBetween(float angleA, float angleB) {
        angleA = normalizeRadianAngle(angleA);
        angleB = normalizeRadianAngle(angleB);

        if (angleA < angleB) {
            return angleB - angleA;
        }
        if (angleA == angleB) {
            return 0;
        }
        return 2 * MathUtils.PI - angleA + angleB;
    }

    public static float sin(float radians) {
        return MathUtils.sinLUT(radians);
    }

    // 判断英雄前进路径上是否有障碍物
    public static boolean isBarrierOverRoute(int fromX, int fromY, int heroRadius, int cx, int cy, int endX, int endY) {
        int height = (int) MathUtils.sqrt(lengthSquared(fromX, fromY, endX, endY));
        float angle = getDegreeAngle(fromX, fromY, endX, endY);
        return isIntersectRectangleCircle(fromX, fromY, heroRadius, height, angle, cx, cy, heroRadius);
    }

    // 判断圆形是否和矩形相交1
    public static boolean isIntersectRectangleCircle(int fromX, int fromY, int halfWidth, int height, float angle,
                                                     int cx, int cy, int r) { // 游戏中会在外面算出angle

        // 快速排除
        // halfWidth + height + r: 通过两边之和大于第三边，得到第三边，绘制圆形对应矩形，做大范围排除
        int threshold = halfWidth + height + r;
        if (Math.abs(cx - fromX) >= threshold) {
            return false;
        }

        if (Math.abs(cy - fromY) >= threshold) {
            return false;
        }

        // 计算矩形中心线到x正半轴需要旋转的角度
        float rotateAngle = getDegreeAngleBetween(angle, 0);
        int toXAfterRotate = fromX + height;

        // 旋转圆心点Left
        int[] p = rotateLeft(fromX, fromY, cx, cy, rotateAngle);

        int cxAfterRotate = p[0];
        int cyAfterRotate = p[1];

        return isIntersectNormalizedRectangleCircle(fromX, toXAfterRotate, fromY, halfWidth, cxAfterRotate,
                cyAfterRotate, r);
    }

    public static boolean isIntersectWithRectangle2(
            int fromX, int fromY, int toX, int toY, int halfWidth, int cx, int cy, int r) {

        // 快速排除
        int threshold = halfWidth + r;
        int minX, maxX;

        if (fromX < toX) {
            minX = fromX;
            maxX = toX;
        } else {
            minX = toX;
            maxX = fromX;
        }

        if (cx < minX - threshold || cx > maxX + threshold) {
            return false;
        }

        int minY, maxY;

        if (fromY < toY) {
            minY = fromY;
            maxY = toY;
        } else {
            minY = toY;
            maxY = fromY;
        }

        if (cy < minY - threshold || cy > maxY + threshold) {
            return false;
        }

        // 计算矩形中心线到x正半轴需要旋转的角度
        float angle = getDegreeAngle(fromX, fromY, toX, toY);
        float rotateAngle = getDegreeAngleBetween(angle, 0);
        int toXAfterRotate = rotateLeft(fromX, fromY, toX, toY, rotateAngle)[0];

        // 旋转圆心点Left
        int[] p = rotateLeft(fromX, fromY, cx, cy, rotateAngle);

        int cxAfterRotate = p[0];
        int cyAfterRotate = p[1];

        return isIntersectNormalizedRectangleCircle(fromX, toXAfterRotate, fromY, halfWidth, cxAfterRotate,
                cyAfterRotate, r);

    }


    // 判断圆形是否和规范化的矩形相交
    public static boolean isIntersectNormalizedRectangleCircle(int fromX, int toX, int y, int halfHeight, int cx,
                                                               int cy, int r) {

        // http://stackoverflow.com/questions/401847/circle-rectangle-collision-detection-intersection

        // 计算矩形中心点
        int centerXOfRectangle = (toX + fromX) / 2;
        int centerYOfRectangle = y;

        int halfWidthOfRectangle = (toX - fromX) / 2;
        int halfHeightOfRectangle = halfHeight;

        // 圆心和矩形中心点的距离
        int dx = Math.abs(cx - centerXOfRectangle);
        int dy = Math.abs(cy - centerYOfRectangle);

        // 圆心距矩形中心过远
        if (dx > (halfWidthOfRectangle + r)) {
            return false;
        }

        if (dy > (halfHeightOfRectangle + r)) {
            return false;
        }

        // 矩形内及矩形的四条边附近
        if (dx <= halfWidthOfRectangle) {
            return true;
        }

        if (dy <= halfHeightOfRectangle) {
            return true;
        }

        // 矩形的四个角
        int dx2 = dx - halfWidthOfRectangle;
        if (Math.abs(dx2) > r) {
            return false;
        }

        int dy2 = dy - halfHeightOfRectangle;
        if (Math.abs(dy2) > r) {
            return false;
        }

        return dx2 * dx2 + dy2 * dy2 <= r * r;
    }

    // 逆时针旋转
    public static int[] rotateLeft(int fromX, int fromY, int toX, int toY, float angle) {

        int deltaX = toX - fromX;
        int deltaY = toY - fromY;
        int myAngle = (int) (angle * 10);
        double sin = cacheSin[myAngle];
        double cos = cacheCos[myAngle];

        int newToX = (int) (fromX + deltaX * cos - deltaY * sin);
        int newToY = (int) (fromY + deltaX * sin + deltaY * cos);

        return new int[]{newToX, newToY};
    }

    // 顺时针旋转
    public static int[] rotateRight(int fromX, int fromY, int toX, int toY, float angle) {

        int deltaX = toX - fromX;
        int deltaY = toY - fromY;

        int myAngle = (int) (angle * 10);
        double sin = cacheSin[myAngle];
        double cos = cacheCos[myAngle];

        int newToX = (int) (fromX + deltaX * cos + deltaY * sin);
        int newToY = (int) (fromY + deltaY * cos - deltaX * sin);

        return new int[]{newToX, newToY};
    }

    // 两点式直线方程求解纵坐标
    public static int getYBytwoPointFormula(int x, int x1, int y1, int x2, int y2) {
        float k = (float) (y2 - y1) / (x2 - x1);
        return (int) (k * (x - x1) + y1);
    }

    //判两圆相交 d＜R+r
    public static boolean isIntersectCircle(int x1, int y1, int r1, int x2, int y2, int r2) {
        int d = r1 + r2;
        int x = Math.abs(x1 - x2);
        if (x > d) {
            return false;
        }
        int y = Math.abs(y1 - y2);
        if (y > d) {
            return false;
        }
        return d * d >= x * x + y * y;
    }


    //判两圆相交 d＜R+r
    public static boolean isIntersectCircle(float x1, float y1, float r1, float x2, float y2, float r2) {
        float d = r1 + r2;
        float x = Math.abs(x1 - x2);
        if (x > d) {
            return false;
        }
        float y = Math.abs(y1 - y2);
        if (y > d) {
            return false;
        }
        return d * d >= x * x + y * y;
    }

    //判两圆相交 d＜R+r
    public static float intersectCircleDistance(float x1, float y1, float r1, float x2, float y2, float r2) {
        float d = r1 + r2;
        float x = Math.abs(x1 - x2);
        if (x > d) {
            return 0;
        }
        float y = Math.abs(y1 - y2);
        if (y > d) {
            return 0;
        }

        float distance = x * x + y * y;
        return d * d >= distance ? (float) Math.sqrt(distance) : 0;
    }

    // 判断点是否在多边形内
    public static boolean isInPolygon(int[] xx, int[] yy, int x, int y) {
        int hits = 0;

        int lastx = xx[xx.length - 1];
        int lasty = yy[yy.length - 1];
        int curx, cury;

        for (int i = 0; i < xx.length; lastx = curx, lasty = cury, i++) {
            curx = xx[i];
            cury = yy[i];

            if (cury == lasty) {
                continue;
            }

            int leftx;
            if (curx < lastx) {
                if (x >= lastx) {
                    continue;
                }
                leftx = curx;
            } else {
                if (x >= curx) {
                    continue;
                }
                leftx = lastx;
            }

            double test1, test2;
            if (cury < lasty) {
                if (y < cury || y >= lasty) {
                    continue;
                }
                if (x < leftx) {
                    hits++;
                    continue;
                }
                test1 = x - curx;
                test2 = y - cury;
            } else {
                if (y < lasty || y >= cury) {
                    continue;
                }
                if (x < leftx) {
                    hits++;
                    continue;
                }
                test1 = x - lastx;
                test2 = y - lasty;
            }

            if (test1 < (test2 / (lasty - cury) * (lastx - curx))) {
                hits++;
            }
        }

        return ((hits & 1) != 0);
    }


    public static boolean isInRectangle(int x1, int y1, int x2, int y2, int x, int y) {
        return x >= x1 && x <= x2 && y >= y1 && y <= y2;
    }

    // 判断点是否在圆形范围内
    public static boolean isInCircle(int cx, int cy, int r, int x, int y) {
        int dx = x - cx;
        if (Math.abs(dx) > r) {
            return false;
        }

        int dy = y - cy;
        if (Math.abs(dy) > r) {
            return false;
        }

        return dx * dx + dy * dy <= r * r;
    }

    // 判断点在射线的哪一侧，0是在直线上，<0是左侧，>0是右侧
    private static int getRelativePosWithLine(
            int fromX, int fromY, int toX, int toY, int x, int y) {
        // 叉积：http://blog.csdn.net/orbit/article/details/7082678

        int r = (x - fromX) * (toY - fromY) - (y - fromY) * (toX - fromX);

        if (r < 0) {
            return -1;
        }

        if (r > 0) {
            return 1;
        }

        return 0;
    }

    // 判断点是否在直线上
    private static boolean isOnLine(int fromX, int fromY, int toX, int toY, int x, int y) {
        return getRelativePosWithLine(fromX, fromY, toX, toY, x, y) == 0;
    }

    // 判断点是否在直线的左侧
    public static boolean isOnLineLeftSide(int fromX, int fromY, int toX, int toY, int x, int y) {
        return getRelativePosWithLine(fromX, fromY, toX, toY, x, y) < 0;
    }

    // 判断点是否在直线的右侧
    public static boolean isOnLineRightSide(int fromX, int fromY, int toX, int toY, int x, int y) {
        return getRelativePosWithLine(fromX, fromY, toX, toY, x, y) > 0;
    }

    // 判断点是否在线段上
    public static boolean isOnLineSegment(int fromX, int fromY, int toX, int toY, int x, int y) {

        int x1, x2;

        if (fromX < toX) {
            x1 = fromX;
            x2 = toX;
        } else {
            x1 = toX;
            x2 = fromX;
        }

        if (x < x1 || x > x2) {
            return false;
        }

        int y1, y2;

        if (fromY < toY) {
            y1 = fromY;
            y2 = toY;
        } else {
            y1 = toY;
            y2 = fromY;
        }

        if (y < y1 || y > y2) {
            return false;
        }

        return isOnLine(fromX, fromY, toX, toY, x, y);
    }

    // 判断两线段是否相交
    // http://www.geeksforgeeks.org/check-if-two-given-line-segments-intersect/
    public static boolean isIntersectWithLine(
            int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4) {

        int r1 = getRelativePosWithLine(x1, y1, x2, y2, x3, y3);
        int r2 = getRelativePosWithLine(x1, y1, x2, y2, x4, y4);

        int r3 = getRelativePosWithLine(x3, y3, x4, y4, x1, y1);
        int r4 = getRelativePosWithLine(x3, y3, x4, y4, x2, y2);

        if (r1 != r2 && r3 != r4) {
            return true;
        }

        if (r1 == 0 && isOnLineSegment(x1, y1, x2, y2, x3, y3)) {
            return true;
        }

        if (r2 == 0 && isOnLineSegment(x1, y1, x2, y2, x4, y4)) {
            return true;
        }

        if (r3 == 0 && isOnLineSegment(x3, y3, x4, y4, x1, y1)) {
            return true;
        }

        if (r4 == 0 && isOnLineSegment(x3, y3, x4, y4, x2, y2)) {
            return true;
        }

        return false;
    }

    /**
     * 判定两矩形是否重叠
     */
    // https://www.geeksforgeeks.org/find-two-rectangles-overlap/?ref=leftbar-rightbar
    public static boolean isIntersectRectangle(int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4) {
        // If one rectangle is on left side of other
        if (x1 >= x4 || x3 >= x2) {
            return false;
        }

        // If one rectangle is above other
        if (y1 <= y3 || y4 <= y2) {
            return false;
        }
        return true;
    }

    /**
     * 判断圆是否与线段相交
     **/
    public static boolean isCircleOnLine(Vec2 from, Vec2 to, Vec2 circle, float r) {
        double distance = pointToLine(from, to, circle);
        return distance <= r;
    }

    /**
     * 根据正弦值获得夹角的角度
     **/
    public static float getDegreesBySin(float sin) {
        return (float) Math.toDegrees(Math.asin(sin));
    }

    /**
     * 计算战斗胜利评分
     */
    public static int getWinBattleRating(int totalHeroCount, int deadHeroCount) {
        if (deadHeroCount == 0) { // 全部存活额外加1颗星
            return totalHeroCount + 1;
        } else {
            return totalHeroCount - deadHeroCount;
        }
    }

    /**
     * 获取2连击，碰撞第一个时的坐标
     *
     * @param x1
     * @param y1
     * @param x2
     * @param y2
     * @param x3
     * @param y3
     * @param sumLength
     * @param scale
     * @return
     */
    public static Vec2 getDoubleHitPoint(float x1, float y1, float x2, float y2, float x3, float y3, float sumLength, float scale) {
        float r1 = BattleUtils.getRadianAngle(x2, y2, x3, y3);
        float r2 = BattleUtils.getRadianAngle(x1, y1, x2, y2);
        float theta = (float) (Math.PI - Math.abs(r1 - r2)) / 2 * (r1 > r2 ? -1 : 1);
        float k = sumLength / BattleUtils.lineSpace(x1, y1, x2, y2);
        float x = (x1 - x2) * k;
        float y = (y1 - y2) * k;
        Vec2 vec = BattleUtils.rotate(new Vec2(x, y), theta).mulLocal(scale);
        return new Vec2(x2 + vec.x, y2 + vec.y);
    }
}
